use models::ReceivedSecondaryMessage;
pub(crate) use models::{ConnectionEvent, ConnectionOptions, HSMSOtptions};
use quic_secs_driver::{
    enums::{ConnectionMode, ConnectionStatus, LogLevel},
    hsms::HsmsConnectionOptions,
    secs2::{
        Message, MessageHead, MessageWrapperForReceived, MessageWrapperForSend, SecsTransaction,
        Value,
    },
    SecsClient, SecsError,
};
use std::{
    fmt::Debug,
    net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr},
    path::PathBuf,
    str::FromStr,
    sync::Arc,
    time::Duration,
};
use tauri::AppHandle;
use tauri_plugin_clipboard_manager::ClipboardExt;

use crate::{fill_path_internal, helper, logger::Logger, pool, AppError, AppResult};
mod models;

macro_rules! log_received_message {
    ($logger:expr_2021,$time:expr_2021,$message:expr_2021) => {
        if let Some(logger) = $logger.as_ref() {
            let str_message = format!("Recv {:?}", $message);
            let str_time = helper::format_time_full($time);
            if logger.is_write_secs1 {
                let str_secs1 = gen_secs1_str(
                    &$message.head,
                    "Recv",
                    &$message.head_raw,
                    &$message.body_raw,
                );
                logger
                    .info_secs1_async(str_time, str_message, Some(str_secs1))
                    .await;
            } else {
                logger.info_async(str_time, str_message).await;
            }
        }
    };
    ($id:expr_2021,$logger:expr_2021, $time:expr_2021, $message:expr_2021) => {
        if let Ok(json) = serde_json::to_string($message) {
            _ = crate::channel_send(ConnectionEvent::new_received_primary_message(
                $id, $time, json,
            ));
        }
        log_received_message!($logger, $time, $message);
    };
}

macro_rules! log_on_message_action {
    ($id:expr_2021, $is_write_secs1:expr_2021,$action:expr_2021, $str_time:expr_2021, $str_message:expr_2021, $message_head:expr_2021,$message_head_raw:expr_2021,$message_body_raw:expr_2021) => {
        let str_secs1 = if $is_write_secs1 {
            Some(gen_secs1_str(
                $message_head,
                $action,
                $message_head_raw,
                $message_body_raw,
            ))
        } else {
            None
        };
        tokio::spawn(async move {
            log_internal($id, $str_time, $str_message, str_secs1).await;
        });
    };
}

macro_rules! reply_secondary {
    ($id:expr_2021,$transaction:expr_2021, $secondary:expr_2021, $logger:expr_2021) => {
        match $transaction.send_secondary($secondary).await {
            Ok(_) => {}
            Err(err) => {
                log_with_ui(
                    $id,
                    $logger,
                    LogLevel::Error,
                    format!("error on reply: {:?}", err),
                )
                .await;
            }
        }
    };
}

/// 建立连接
#[tauri::command]
pub(crate) async fn secs_connect(id: u64) -> AppResult<()> {
    let mut guard = pool::ALL_CONNS.write().await;
    if let Some(connection) = guard.get_mut(&id) {
        let mut path =
            PathBuf::from_str(&connection.preference.log.dir).or(Err(AppError::Message(
                format!("log dir {} is invalid path", connection.preference.log.dir),
            )))?;
        fill_path_internal(&mut path);
        if let Some(o) = connection.client.take() {
            o.shutdown().await;
        }
        _ = connection.logger.take();
        if connection.configuration.protocol == 1 {
            let hsms = connection
                .configuration
                .hsms
                .as_ref()
                .ok_or_else(|| AppError::Message("hsms option is none".to_string()))?;
            connection.client.replace(Arc::new(connect_hsms(
                id,
                connection.configuration.device_id,
                connection.configuration.system_byte,
                connection.preference.log.is_write_secs1,
                hsms,
            )?));
        } else {
            return Err(AppError::Message("secs1 not implemented".to_string()));
        }
        let logger = Logger::new(
            path,
            connection.configuration.identity.clone(),
            connection.preference.log.file_max_size * 1024 * 1024,
            connection.preference.log.is_write_secs1,
        );
        connection.logger.replace(logger);
    }
    Ok(())
}

#[tauri::command]
pub(crate) async fn secs_shutdown(id: u64) {
    let mut guard = pool::ALL_CONNS.write().await;
    if let Some(connection) = guard.get_mut(&id) {
        if let Some(o) = connection.client.take() {
            o.shutdown().await;
        }
        _ = connection.logger.take();
    }
}

#[tauri::command]
pub(crate) async fn secs_send_primary(
    id: u64,
    tx_id: String,
) -> AppResult<Option<ReceivedSecondaryMessage>> {
    let mut message = None;
    let mut client = None;
    let mut logger = None;
    let mut preference = None;
    let guard = pool::ALL_CONNS.read().await;
    if let Some(connection) = guard.get(&id) {
        connection
            .transactions
            .iter()
            .find(|m| m.id == tx_id)
            .map(|x| message.replace(x.clone()));
        connection
            .client
            .as_ref()
            .map(|x| client.replace(x.clone()));
        connection
            .logger
            .as_ref()
            .map(|x| logger.replace(x.clone()));
        preference.replace(connection.preference.clone());
    }
    drop(guard);
    let mut message = message
        .ok_or_else(|| AppError::Message(format!("couldn't find the transaction by id {tx_id}")))?;
    let client =
        client.ok_or_else(|| AppError::Message(format!("couldn't get the client id {id}")))?;
    match client.send_primary_message(&mut message.primary).await? { Some(mut secondary) => {
        if let Some(secondary_template) = message.secondary.as_ref() {
            helper::fill_describe(&mut secondary, secondary_template, preference.as_ref());
        }
        secondary.name.replace(format!(
            "S{}F{}",
            secondary.head.stream, secondary.head.function
        ));
        let time = chrono::Local::now();
        log_received_message!(logger, &time, &secondary);
        tokio::time::sleep(Duration::from_millis(50)).await;
        Ok(Some(ReceivedSecondaryMessage {
            time: helper::format_time_h_m_s_3f(&time),
            message: secondary,
        }))
    } _ => {
        Ok(None)
    }}
}

// #[tauri::command]
// pub(crate) async fn secs_check_identity(id: Option<u64>, identity: String) -> Option<String> {
//     pool::check_identity(id, identity)
// }

fn connect_hsms(
    id: u64,
    device_id: u16,
    system_byte: u32,
    is_write_secs1: bool,
    hsms: &HSMSOtptions,
) -> AppResult<SecsClient> {
    let (socket_addr, options) = {
        if hsms.mode == 0 {
            let addr = parse_to_ip_addr(&hsms.remote_ip)?;
            (
                SocketAddr::new(addr, hsms.remote_port),
                HsmsConnectionOptions::new(
                    ConnectionMode::Active,
                    Duration::from_millis(hsms.t3),
                    Duration::from_millis(hsms.t5),
                    Duration::from_millis(hsms.t6),
                    Duration::from_millis(hsms.t7),
                    Duration::from_millis(hsms.t8),
                    Duration::from_millis(hsms.link_test),
                ),
            )
        } else {
            let addr = parse_to_ip_addr(&hsms.local_ip)?;
            (
                SocketAddr::new(addr, hsms.local_port),
                HsmsConnectionOptions::new(
                    ConnectionMode::Passive,
                    Duration::from_millis(hsms.t3),
                    Duration::from_millis(hsms.t5),
                    Duration::from_millis(hsms.t6),
                    Duration::from_millis(hsms.t7),
                    Duration::from_millis(hsms.t8),
                    Duration::from_millis(hsms.link_test),
                ),
            )
        }
    };
    let options =
        quic_secs_driver::SecsConnectionOptions::new_hsms(device_id, socket_addr, options)
            .log_callback(on_log(id))
            .on_message_received(on_message_received(id, is_write_secs1))
            .on_message_send(on_message_send(id, is_write_secs1))
            .on_connection_status_changed(on_connection_status_changed(id))
            .on_primary_message_received(on_primary_message_received(id))
            .system_byte(system_byte);
    let client = SecsClient::new(options);
    client.start_connect()?;
    Ok(client)
}

fn parse_to_ip_addr(ip: &str) -> AppResult<IpAddr> {
    let addr = Ipv4Addr::from_str(ip);
    if let Ok(addr) = addr {
        return Ok(IpAddr::V4(addr));
    }
    let addr = Ipv6Addr::from_str(ip);
    if let Ok(addr) = addr {
        return Ok(IpAddr::V6(addr));
    }
    Err(AppError::Message(format!("{ip} is not a valid ip address")))
}

fn on_log(id: u64) -> impl Fn(LogLevel, String, Option<SecsError>) {
    move |level: LogLevel, message: String, error: Option<SecsError>| {
        let time = chrono::Local::now();
        let str_time = helper::format_time_full(&time);
        let msssage_clone = message.clone();
        let str_time_clone = str_time.clone();
        tokio::spawn(async move {
            let guard = pool::ALL_CONNS.read().await;
            if let Some(connection) = guard.get(&id) {
                if let Some(logger) = connection.logger.as_ref() {
                    logger.log_async(str_time_clone, level, msssage_clone).await;
                }
            }
        });
        _ = crate::channel_send(ConnectionEvent::new_log_output(
            id,
            str_time,
            level,
            message,
            error.map(|err| err.to_string()),
        ));
    }
}
fn on_connection_status_changed(id: u64) -> impl Fn(ConnectionStatus, ConnectionStatus) {
    move |previous: ConnectionStatus, current: ConnectionStatus| {
        _ = crate::channel_send(ConnectionEvent::new_connection_status_changed(
            id, previous, current,
        ));
    }
}

fn on_primary_message_received(
    id: u64,
) -> impl Fn(SecsTransaction) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + Sync>>
{
    move |mut transaction: SecsTransaction| {
        Box::pin(async move {
            let time = chrono::Local::now();
            let guard = pool::ALL_CONNS.read().await;
            let mut logger = None;
            let mut template = None;
            let mut preference = None;
            if let Some(connection) = guard.get(&id) {
                helper::find_matched_transaction(
                    &transaction.primary,
                    &connection.transactions,
                    crate::models::MatchPolicy::FormatAndValue,
                )
                .map(|x| template.replace(x.clone()));
                connection
                    .logger
                    .as_ref()
                    .map(|x| logger.replace(x.clone()));
                preference.replace(connection.preference.clone());
            }
            drop(guard);
            match template { Some(tx_template) => {
                helper::fill_describe(
                    &mut transaction.primary,
                    &tx_template.primary,
                    preference.as_ref(),
                );
                log_received_message!(id, logger, &time, &transaction.primary);
                if transaction.primary.head.is_expect_reply {
                    if let Some(secondary) = tx_template.secondary {
                        reply_secondary!(id, transaction, secondary, &logger);
                        return;
                    }
                }
            } _ => {
                transaction.primary.name.replace(format!(
                    "S{}F{}",
                    transaction.primary.head.stream, transaction.primary.head.function
                ));
                log_received_message!(id, logger, &time, &transaction.primary);
            }}
            if transaction.primary.head.is_expect_reply {
                let mut message = Message::new(transaction.primary.head.stream, 0, false);
                message.name.replace(format!(
                    "S{}F{}",
                    message.head.stream, message.head.function
                ));
                reply_secondary!(id, transaction, message, &logger);
            }
        })
    }
}

fn on_message_received(id: u64, is_write_secs1: bool) -> impl Fn(&mut MessageWrapperForReceived) {
    move |message: &mut MessageWrapperForReceived| {
        if message.head.stream == 0 {
            let str_message = format!("Recv {:?}", message);
            let str_time = helper::get_log_time();
            // 输出日志到界面
            _ = crate::channel_send(ConnectionEvent::new_log_output(
                id,
                str_time.clone(),
                LogLevel::Info,
                str_message.clone(),
                None,
            ));
            log_on_message_action!(
                id,
                is_write_secs1,
                "Recv",
                str_time,
                str_message,
                &message.head,
                &message.head_raw,
                &message.body_raw
            );
        } else if message.head.stream == 9 {
            // S9Fx的消息当 Primary 处理
            let time = chrono::Local::now();
            // 输出消息到界面
            if let Ok(json) = serde_json::to_string(message) {
                _ = crate::channel_send(ConnectionEvent::new_received_primary_message(
                    id, &time, json,
                ));
            }
            let str_time = helper::format_time_full(&time);
            let str_message = format!("Recv {:?}", message);
            log_on_message_action!(
                id,
                is_write_secs1,
                "Recv",
                str_time,
                str_message,
                &message.head,
                &message.head_raw,
                &message.body_raw
            );
        }
    }
}

fn on_message_send(id: u64, is_write_secs1: bool) -> impl Fn(MessageWrapperForSend) {
    move |wrappger: MessageWrapperForSend| {
        if wrappger.message.head.stream == 0 {
            let str_message = format!("Send {:?}", wrappger.message);
            let str_time = helper::get_log_time();
            _ = crate::channel_send(ConnectionEvent::new_log_output(
                id,
                str_time.clone(),
                LogLevel::Info,
                str_message.clone(),
                None,
            ));
            log_on_message_action!(
                id,
                is_write_secs1,
                "Send",
                str_time,
                str_message,
                &wrappger.message.head,
                &wrappger.head_raw,
                &wrappger.body_raw
            );
        } else {
            let time = chrono::Local::now();
            if let Ok(json) = serde_json::to_string(wrappger.message) {
                _ = crate::channel_send(ConnectionEvent::new_send_message(
                    id,
                    &time,
                    json,
                    wrappger.message.head.function % 2 == 1 && wrappger.message.head.stream != 9,
                ));
            }
            let str_time = helper::format_time_full(&time);
            let str_message = format!("Send {:?}", wrappger.message);
            log_on_message_action!(
                id,
                is_write_secs1,
                "Send",
                str_time,
                str_message,
                &wrappger.message.head,
                &wrappger.head_raw,
                &wrappger.body_raw
            );
        }
    }
}

async fn log_internal(id: u64, str_time: String, str_message: String, str_secs1: Option<String>) {
    let guard = pool::ALL_CONNS.read().await;
    if let Some(connection) = guard.get(&id) {
        if let Some(logger) = connection.logger.as_ref() {
            logger
                .info_secs1_async(str_time, str_message, str_secs1)
                .await;
        }
    }
}

async fn log_with_ui(id: u64, logger: &Option<Logger>, level: LogLevel, message: String) {
    // let time = chrono::Local::now();
    let time = helper::get_log_time();
    if let Some(logger) = logger {
        logger.log_async(time.clone(), level, message.clone()).await;
    }
    _ = crate::channel_send(ConnectionEvent::new_log_output(
        id, time, level, message, None,
    ));
}

fn gen_secs1_str<T: Debug>(
    head: &MessageHead,
    action: &str,
    head_raw: &[u8],
    body_raw: &Option<T>,
) -> String {
    if let Some(body_raw) = body_raw {
        format!(
            "{action} {:?}{}{:?}{}{:?}",
            head,
            crate::logger::NEWLINE,
            head_raw,
            crate::logger::NEWLINE,
            body_raw
        )
    } else {
        format!(
            "{action} {:?}{}{:?}",
            head,
            crate::logger::NEWLINE,
            head_raw
        )
    }
}

#[tauri::command]
pub(crate) fn secs_copy_message_text(app: AppHandle, message: Message) {
    _ = app.clipboard().write_text(format!("{:?}", message).trim());
}

#[tauri::command]
pub(crate) fn secs_copy_value_text(app: AppHandle, value: Value) {
    _ = app.clipboard().write_text(format!("{:?}", value).trim());
}
